package com.qiangesoft.log.core;

import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.qiangesoft.log.entity.OperationLog;
import com.qiangesoft.log.utils.IpUtil;
import com.qiangesoft.log.utils.ServletUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.ip2region.core.Ip2regionSearcher;
import net.dreamlu.mica.ip2region.core.IpInfo;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.NamedThreadLocal;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Map;

/**
 * 操作日志记录处理
 *
 * @author qiangesoft
 * @date 2024-04-02
 */
@Slf4j
@RequiredArgsConstructor
@Aspect
@Component
public class LogAspect {

    private final Ip2regionSearcher ip2regionSearcher;

    /**
     * 消耗时间
     */
    private static final ThreadLocal<Long> THREAD_COST_TIME = new NamedThreadLocal<>("CostTime");

    /**
     * 处理请求前执行
     *
     * @param joinPoint
     * @param log
     */
    @Before(value = "@annotation(log)")
    public void doBefore(JoinPoint joinPoint, Log log) {
        THREAD_COST_TIME.set(System.currentTimeMillis());
    }

    /**
     * 处理请求后执行
     *
     * @param joinPoint
     * @param log
     * @param jsonResult
     */
    @AfterReturning(pointcut = "@annotation(log)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Log log, Object jsonResult) {
        handleLog(joinPoint, log, jsonResult, null);
    }

    /**
     * 请求发生异常
     *
     * @param joinPoint
     * @param log
     * @param e
     */
    @AfterThrowing(value = "@annotation(log)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log log, Exception e) {
        handleLog(joinPoint, log, null, e);
    }

    /**
     * 处理日志
     *
     * @param joinPoint
     * @param log
     * @param jsonResult
     * @param e
     */
    private void handleLog(final JoinPoint joinPoint, Log log, Object jsonResult, final Exception e) {
        try {
            OperationLog operationLog = new OperationLog();
            // 业务信息
            operationLog.setModule(log.module());
            operationLog.setOperationName(log.operationName());
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            operationLog.setMethod(className + "." + methodName + "()");

            // 请求信息
            HttpServletRequest request = ServletUtil.getRequest();
            operationLog.setRequestUrl(request.getRequestURI());
            operationLog.setRequestMethod(request.getMethod());
            String ipAddr = IpUtil.getIpAddr(request);
            operationLog.setRequestIp(ipAddr);
            IpInfo ipInfo = ip2regionSearcher.memorySearch(ipAddr);
            if (ipInfo != null) {
                operationLog.setRequestLocation(StringUtils.isNotBlank(ipInfo.getCountry()) ? ipInfo.getCountry() + ipInfo.getProvince() + ipInfo.getCity() + ipInfo.getIsp() : ipInfo.getIsp());
            }
            operationLog.setRequestParam(getRequestParam(joinPoint));

            // 响应信息
            operationLog.setSuccessFlag(true);
            operationLog.setResponseData(jsonResult == null ? null : JSONObject.toJSONString(jsonResult));
            if (e != null) {
                operationLog.setSuccessFlag(false);
                operationLog.setErrorMsg(e.getMessage());
            }
            operationLog.setCostTime(System.currentTimeMillis() - THREAD_COST_TIME.get());

            // 入库
            SpringUtil.publishEvent(new OperationLogEvent(operationLog));
        } catch (Exception exp) {
            exp.printStackTrace();
        } finally {
            THREAD_COST_TIME.remove();
        }
    }

    /**
     * 获取请求的参数
     *
     * @param joinPoint
     * @return
     */
    private String getRequestParam(JoinPoint joinPoint) {
        HttpServletRequest request = ServletUtil.getRequest();
        Map<?, ?> paramsMap = ServletUtil.getParamMap(request);
        String requestMethod = request.getMethod();
        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
            StringBuilder param = new StringBuilder();
            Object[] params = joinPoint.getArgs();
            if (params != null) {
                for (Object o : params) {
                    if (o != null && !isFilterObject(o)) {
                        try {
                            param.append(JSON.toJSONString(o)).append(" ");
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            return param.toString().trim();
        } else {
            return JSON.toJSONString(paramsMap);
        }
    }

    /**
     * 判断是否需要过滤的对象
     *
     * @param o
     * @return
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof BindingResult;
    }
}
